/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

/**
 * Component that adds physics to its parent game object. This component
 * implements force calculation based on mass, impulses, friction, and
 * collisions.
 */
public class PhysicsComponent extends GameComponent {

	private float mMass;
	private float mBounciness; // 1.0 = super bouncy, 0.0 = zero bounce
	private float mInertia;
	private float mStaticFrictionCoeffecient;
	private float mDynamicFrictionCoeffecient;

	private static final float DEFAULT_MASS = 1.0f;
	private static final float DEFAULT_BOUNCINESS = 0.1f;
	private static final float DEFAULT_INERTIA = 0.01f;
	private static final float DEFAULT_STATIC_FRICTION_COEFFECIENT = 0.05f;
	private static final float DEFAULT_DYNAMIC_FRICTION_COEFFECIENT = 0.02f;

	PhysicsComponent() {
		super();
		reset();
		setPhase(ComponentPhases.POST_PHYSICS.ordinal());
	}

	@Override
	public void reset() {
		// TODO: no reason to call accessors here locally.
		setMass(DEFAULT_MASS);
		setBounciness(DEFAULT_BOUNCINESS);
		setInertia(DEFAULT_INERTIA);
		setStaticFrictionCoeffecient(DEFAULT_STATIC_FRICTION_COEFFECIENT);
		setDynamicFrictionCoeffecient(DEFAULT_DYNAMIC_FRICTION_COEFFECIENT);
	}

	@Override
	public void update(float timeDelta, BaseObject parent) {
		GameObject parentObject = (GameObject) parent;

		// we look to user data so that other code can provide impulses
		Vector2 impulseVector = parentObject.getImpulse();

		final Vector2 currentVelocity = parentObject.getVelocity();

		final Vector2 surfaceNormal = parentObject
				.getBackgroundCollisionNormal();
		if (surfaceNormal.length2() > 0.0f) {
			resolveCollision(currentVelocity, impulseVector, surfaceNormal,
					impulseVector);
		}

		VectorPool vectorPool = sSystemRegistry.vectorPool;

		// if our speed is below inertia, we need to overcome inertia before we
		// can move.

		boolean physicsCausesMovement = true;

		final float inertiaSquared = getInertia() * getInertia();

		Vector2 newVelocity = vectorPool.allocate(currentVelocity);
		newVelocity.add(impulseVector);

		if (newVelocity.length2() < inertiaSquared) {
			physicsCausesMovement = false;
		}

		final boolean touchingFloor = parentObject.touchingGround();

		GravityComponent gravity = parentObject
				.findByClass(GravityComponent.class);

		if (touchingFloor && currentVelocity.y <= 0.0f
				&& Math.abs(newVelocity.x) > 0.0f && gravity != null) {
			final Vector2 gravityVector = gravity.getGravity();

			// if we were moving last frame, we'll use dynamic friction. Else
			// static.
			float frictionCoeffecient = Math.abs(currentVelocity.x) > 0.0f ? getDynamicFrictionCoeffecient()
					: getStaticFrictionCoeffecient();
			frictionCoeffecient *= timeDelta;

			// Friction = cofN, where cof = friction coefficient and N = force
			// perpendicular to the ground.
			final float maxFriction = Math.abs(gravityVector.y) * getMass()
					* frictionCoeffecient;

			if (maxFriction > Math.abs(newVelocity.x)) {
				newVelocity.x = (0.0f);
			} else {
				newVelocity.x = (newVelocity.x - (maxFriction * Utils
						.sign(newVelocity.x)));
			}
		}

		if (Math.abs(newVelocity.x) < 0.01f) {
			newVelocity.x = (0.0f);
		}

		if (Math.abs(newVelocity.y) < 0.01f) {
			newVelocity.y = (0.0f);
		}

		// physics-based movements means constant acceleration, always. set the
		// target to the
		// velocity.
		if (physicsCausesMovement) {
			parentObject.setVelocity(newVelocity);
			parentObject.setTargetVelocity(newVelocity);
			parentObject.setAcceleration(Vector2.ZERO);
			parentObject.setImpulse(Vector2.ZERO);
		}

		vectorPool.release(newVelocity);
	}

	protected void resolveCollision(Vector2 velocity, Vector2 impulse,
			Vector2 opposingNormal, Vector2 outputImpulse) {
		VectorPool vectorPool = sSystemRegistry.vectorPool;

		outputImpulse.set(impulse);

		Vector2 collisionNormal = vectorPool.allocate(opposingNormal);

		collisionNormal.normalize();

		Vector2 relativeVelocity = vectorPool.allocate(velocity);
		relativeVelocity.add(impulse);

		final float dotRelativeAndNormal = relativeVelocity
				.dot(collisionNormal);

		// make sure the motion of the entity requires resolution
		if (dotRelativeAndNormal < 0.0f) {
			final float coefficientOfRestitution = getBounciness(); // 0 =
																	// perfectly
																	// inelastic,
																	// 1 =
																	// perfectly
																	// elastic

			// calculate an impulse to apply to the entity
			float j = (-(1 + coefficientOfRestitution) * dotRelativeAndNormal);

			j /= ((collisionNormal.dot(collisionNormal)) * (1 / getMass()));

			Vector2 entity1Adjust = vectorPool.allocate(collisionNormal);

			entity1Adjust.set(collisionNormal);
			entity1Adjust.multiply(j);
			entity1Adjust.divide(getMass());
			entity1Adjust.add(impulse);
			outputImpulse.set(entity1Adjust);
			vectorPool.release(entity1Adjust);

		}

		vectorPool.release(collisionNormal);
		vectorPool.release(relativeVelocity);
	}

	protected void resolveCollision(Vector2 velocity, Vector2 impulse,
			Vector2 opposingNormal, float otherMass, Vector2 otherVelocity,
			Vector2 otherImpulse, float otherBounciness, Vector2 outputImpulse) {
		VectorPool vectorPool = sSystemRegistry.vectorPool;

		Vector2 collisionNormal = vectorPool.allocate(opposingNormal);
		collisionNormal.normalize();

		Vector2 entity1Velocity = vectorPool.allocate(velocity);
		entity1Velocity.add(impulse);

		Vector2 entity2Velocity = vectorPool.allocate(otherVelocity);
		entity2Velocity.add(otherImpulse);

		Vector2 relativeVelocity = vectorPool.allocate(entity1Velocity);
		relativeVelocity.subtract(entity2Velocity);

		final float dotRelativeAndNormal = relativeVelocity
				.dot(collisionNormal);

		// make sure the entities' motion requires resolution
		if (dotRelativeAndNormal < 0.0f) {
			final float bounciness = Math.min(
					getBounciness() + otherBounciness, 1.0f);
			final float coefficientOfRestitution = bounciness; // 0 = perfectly
																// inelastic,
																// 1 = perfectly
																// elastic

			// calculate an impulse to apply to both entities
			float j = (-(1 + coefficientOfRestitution) * dotRelativeAndNormal);

			j /= ((collisionNormal.dot(collisionNormal)) * (1 / getMass() + 1 / otherMass));

			Vector2 entity1Adjust = vectorPool.allocate(collisionNormal);
			entity1Adjust.multiply(j);
			entity1Adjust.divide(getMass());
			entity1Adjust.add(impulse);

			outputImpulse.set(entity1Adjust);

			// TODO: Deal impulses both ways.
			/*
			 * Vector3 entity2Adjust = (collisionNormal j); entity2Adjust[0] /=
			 * otherMass; entity2Adjust[1] /= otherMass; entity2Adjust[2] /=
			 * otherMass;
			 * 
			 * const Vector3 newEntity2Impulse = otherImpulse + entity2Adjust;
			 */

			vectorPool.release(entity1Adjust);
		}

		vectorPool.release(collisionNormal);
		vectorPool.release(entity1Velocity);
		vectorPool.release(entity2Velocity);
		vectorPool.release(relativeVelocity);
	}

	public float getMass() {
		return mMass;
	}

	public void setMass(float mass) {
		mMass = mass;
	}

	public float getBounciness() {
		return mBounciness;
	}

	public void setBounciness(float bounciness) {
		mBounciness = bounciness;
	}

	public float getInertia() {
		return mInertia;
	}

	public void setInertia(float inertia) {
		mInertia = inertia;
	}

	public float getStaticFrictionCoeffecient() {
		return mStaticFrictionCoeffecient;
	}

	public void setStaticFrictionCoeffecient(float staticFrictionCoeffecient) {
		mStaticFrictionCoeffecient = staticFrictionCoeffecient;
	}

	public float getDynamicFrictionCoeffecient() {
		return mDynamicFrictionCoeffecient;
	}

	public void setDynamicFrictionCoeffecient(float dynamicFrictionCoeffecient) {
		mDynamicFrictionCoeffecient = dynamicFrictionCoeffecient;
	}

}
